import ExpoModulesCore

public class HelloWorldExpoModulesModule: Module {

    // Each module class must implement the definition function. The definition consists of components
    // that describes the module's functionality and behavior.
    // See https://docs.expo.dev/modules/module-api for more details about available components.
    public func definition() -> ModuleDefinition {
        // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
        // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
        // The module will be accessible from `requireNativeModule('HelloWorldExpoModules')` in JavaScript.
        Name("HelloWorldExpoModules")

        // Initialize Tencent COS service once when the module is created
        OnCreate {
            PutObject.setupCOSService()
        }

        // Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
        Constants([
            "PI": Double.pi
        ])

        // Defines event names that the module can send to JavaScript.
        Events("onChange")

        // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
        Function("hello") {
            return "Hello world! 👋"
        }

        Function("isSandboxEnvironment") { () -> Bool in
            print("[HelloWorldExpoModules] Checking isSandboxEnvironment...")

            guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else {
                print("[HelloWorldExpoModules] appStoreReceiptURL is nil")
                return false
            }
            print(
                "[HelloWorldExpoModules] appStoreReceiptURL: \(appStoreReceiptURL)"
            )
            let lastPathComponent = appStoreReceiptURL.lastPathComponent

            if lastPathComponent == "sandboxReceipt" {
                return true
            } else {
                return false
            }
        }

        // Defines a JavaScript function that always returns a Promise and whose native code
        // is by default dispatched on the different thread than the JavaScript runtime runs on.
        AsyncFunction("setValueAsync") { (value: String) in
            // Send an event to JavaScript.
            self.sendEvent(
                "onChange",
                [
                    "value": value
                ])
        }

        // Expose Tencent COS upload to JavaScript
        AsyncFunction("putObjectAsync") {
            (bucket: String, key: String, region: String, filePath: String) throws -> String in
            var uploadError: Error?
            var resultKey: String?
            let semaphore = DispatchSemaphore(value: 0)
            PutObject.uploadLocalFile(
                filePath: filePath, objectKey: key, bucket: bucket,
                region: region, progress: nil
            ) { url, qcloudResult, error in
                resultKey = url?.absoluteString
                uploadError = error
                semaphore.signal()
            }
            _ = semaphore.wait(timeout: .distantFuture)
            if let error = uploadError {
                throw NSError(
                    domain: "PutObject", code: -1,
                    userInfo: [NSLocalizedDescriptionKey: "Upload failed: \(error.localizedDescription)"])
            }
            return resultKey ?? key
        }

        // Expose Tencent COS batch upload to JavaScript
        AsyncFunction("batchUploadAsync") {
            (bucket: String, folder: String?, region: String, filePaths: [String]) throws -> [String] in
            var uploadErrors: [(path: String, error: Error?)] = []
            var uploadResults: [String] = []
            let semaphore = DispatchSemaphore(value: 0)

            // Start batch upload
            PutObject.batchUploadLocalFiles(
                filePaths: filePaths,
                bucket: bucket,
                region: region,
                folder: folder,
                onSingleFinish: { index, path, url, qcloudResult, error in
                    uploadErrors.append((path: path, error: error))
                    if let key = url?.absoluteString, error == nil {
                        uploadResults.append(key)
                    }
                },
                onAllFinish: {
                    semaphore.signal()
                }
            )

            // Wait for all uploads to complete
            _ = semaphore.wait(timeout: .distantFuture)
            let failed = uploadErrors.filter { $0.error != nil }
            if !failed.isEmpty {
                let messages = failed.map { "\($0.path): \($0.error!.localizedDescription)" }
                    .joined(separator: "; ")
                throw NSError(domain: "PutObject", code: -1,
                    userInfo: [NSLocalizedDescriptionKey: "Batch upload failed for files: \(messages)"])
            }
            return uploadResults
        }
    }
}
